package service

import (
	"context"
	"database/sql"

	dbmodel "gitee.com/hp-huiw/family-expense-api/internal/db/model"
	"gitee.com/hp-huiw/family-expense-api/internal/repository"
	"gitee.com/hp-huiw/my-go-core/security"
	"gitee.com/hp-huiw/my-go-core/util"
	"github.com/samber/lo"
)

type UserRoleService interface {
	QueryRoleIDs(ctx context.Context, accountGroupID string, userID string) ([]string, error)
	QueryFullRoles(ctx context.Context, accountGroupID string, userID string) ([]security.FullRole, error)
	HasRole(ctx context.Context, accountGroupID string, userID string, roleName string) (bool, error)
	Add(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string, roleIDs []string) error
	Delete(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string, roleIDs []string) error
	InsertWithTx(ctx context.Context, tx *sql.Tx, model *dbmodel.UserRole) error
	BatchInsertWithTx(ctx context.Context, tx *sql.Tx, models []dbmodel.UserRole) error
	DeleteInOrWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error
	DeleteInAndWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error
}

func NewUserRoleService(db *sql.DB) UserRoleService {
	return &userRoleService{
		db:                 db,
		userRoleRepository: repository.NewUserRoleRepository(db),
	}
}

// QueryRoleIDs queries role ids of user.
func (s *userRoleService) QueryRoleIDs(ctx context.Context, accountGroupID string, userID string) ([]string, error) {
	return s.userRoleRepository.QueryRoleIDs(ctx, accountGroupID, userID)
}

// QueryFullRoles queries roles' id, authoritym title based on provided user id against specific account group.
func (s *userRoleService) QueryFullRoles(ctx context.Context, accountGroupID string, userID string) ([]security.FullRole, error) {
	query, err := s.userRoleRepository.QueryFullRoles(ctx, accountGroupID, userID)

	if err != nil {
		return nil, err
	}

	result := lo.Map(query, func(item repository.Role, _ int) security.FullRole {
		return security.FullRole{
			ID:        item.ID,
			Title:     item.Title,
			Authority: *item.Authority,
		}
	})

	return result, nil
}

// HasRole checks if the user with provided id has the specific role.
func (s *userRoleService) HasRole(ctx context.Context, accountGroupID string, userID string, roleName string) (bool, error) {
	return s.userRoleRepository.Exists(ctx, accountGroupID, userID, roleName)
}

// Add adds roles for user.
func (s *userRoleService) Add(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string, roleIDs []string) error {
	models := lo.Map(roleIDs, func(item string, _ int) dbmodel.UserRole {
		return dbmodel.UserRole{
			ID:             util.GenerateUUIDWithoutDashes(),
			AccountGroupID: accountGroupID,
			UserID:         userID,
			RoleID:         item,
		}
	})

	return s.BatchInsertWithTx(ctx, tx, models)
}

// Deletes user role by user id and role ids.
func (s *userRoleService) Delete(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string, roleIDs []string) error {
	return s.userRoleRepository.DeleteWithTx(ctx, tx, accountGroupID, userID, roleIDs)
}

// InsertWithTx inserts user role with transaction.
func (s *userRoleService) InsertWithTx(ctx context.Context, tx *sql.Tx, model *dbmodel.UserRole) error {
	model.ID = util.GenerateUUIDWithoutDashes()
	return s.userRoleRepository.InsertWithTx(ctx, tx, model)
}

// BatchInsertWithTx inserts user roles with transaction.
func (s *userRoleService) BatchInsertWithTx(ctx context.Context, tx *sql.Tx, models []dbmodel.UserRole) error {
	for i := range models {
		models[i].ID = util.GenerateUUIDWithoutDashes()
	}

	return s.userRoleRepository.BatchInsertWithTx(ctx, tx, models)
}

// DeleteInOrWithTx deletes all user roles by account group id or user id with transaction.
func (s *userRoleService) DeleteInOrWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error {
	return s.userRoleRepository.DeleteInOrWithTx(ctx, tx, accountGroupID, userID)
}

// DeleteInAndWithTx deletes all user roles by account group id and user id with transaction.
func (s *userRoleService) DeleteInAndWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error {
	return s.userRoleRepository.DeleteInAndWithTx(ctx, tx, accountGroupID, userID)
}

// Container of UserRoleService functions.
type userRoleService struct {
	db                 *sql.DB
	userRoleRepository repository.UserRoleRepository
}
